import compiler
import arithmetic_compiler

redraw_function = lambda: None

class ExpressionType(object):
  SIMPLE, MATH, BOOL = range(3)

class Expression(object):
  def __init__(self, expression, variables, eq_type=ExpressionType.SIMPLE, has_eq=False):
    self._variables = variables
    self._eq_type = eq_type
    self._has_eq = has_eq
    self.expression = self._rearrange_expression(expression)
    self._is_readonly = self._calculate_is_readonly()

  def _rearrange_expression(self, expression):
    if not self._has_eq:
      return expression
    lhs, rhs = expression.split("==")
    try:
      eval(lhs, {}, {})
      return "%s==%s" % (rhs, lhs)
    except NameError:
      return expression

  def _calculate_is_readonly(self):
    if not self._variables:
      return True
    to_check = self.expression
    if self._has_eq:
      to_check = self.expression.split("==")[0]
    var_name = self._find_single_var_name(to_check)
    if var_name is None:
      return True
    if self._eq_type == ExpressionType.BOOL:
      # If the to_check part is const, the expression is actually readonly.
      if eval(to_check, {}, {var_name: True}) == eval(to_check, {}, {var_name: False}):
        return True
    return False

  def getVariables(self):
    return self._variables.copy()

  def setReadonly(self):
    self._is_readonly = True

  def isReadonly(self):
    return self._is_readonly

  def _evaluateBool(self, expression, var_name, value):
    vars = {k: v.value for k, v in self._variables.iteritems()}
    vars[var_name] = True
    if eval(expression, {}, vars) == value:
      return True
    else:
      return False

  def _find_single_var_name(self, expression):
    tree = compiler.parse(expression)
    class Visitor(object):
      def __init__(self):
        self.name = ""
      def visitName(self, node):
        name = node.name
        if self.name != "" and self.name != name:
          self.name = None
        else:
          self.name = name
    visitor = Visitor()
    compiler.walk(tree, visitor)
    if visitor.name == "":
      visitor.name = None
    return visitor.name

  def evaluateTo(self, value, ignore=()):
    if self.isReadonly():
      raise ReadonlyError, "Can't evaluate-to a readonly expression"
    if self._has_eq:
      lhs_var_name = self._find_single_var_name(self.expression.split("==")[0])
      rhs_expression = self.expression.split("==")[1]
      if type(value) != bool:
        raise TypeError, "Expressions with '=' must be evaluated to boolean"
      if self._eq_type == ExpressionType.SIMPLE:
        rhs_value = self._calculateValueOf(rhs_expression)
        if type(rhs_value) == bool:
          self._variables[lhs_var_name].setValue(rhs_value == value, ignore)
        elif not value:
          raise TypeError, "Math equation exception can be evaluated only to True"
        else:
          self._variables[lhs_var_name].setValue(rhs_value, ignore)
      elif self._eq_type == ExpressionType.MATH:
        if value == False:
          raise TypeError, "Math equation exception can be evaluated only to True"
        params = [k for k in self._variables if k != lhs_var_name]
        param_str = ",".join(params)
        param_values = [self._variables[k].value for k in params]
        arithmetic_code = "func(%s) { %s }" % (param_str, self.expression.replace("==", "="))
        d = arithmetic_compiler.compile(arithmetic_code)["func"](*(self._variables[x].value for x in params))
        self._variables[lhs_var_name].setValue(d[lhs_var_name], ignore)
      elif self._eq_type == ExpressionType.BOOL:
        self._variables[lhs_var_name].setValue(self._evaluateBool(rhs_expression, lhs_var_name, value), ignore)
      else:
        raise TypeError, "Wrong expression type"
    else:
      if self._eq_type == ExpressionType.SIMPLE:
        self._variables.values()[0].setValue(value, ignore)
      elif self._eq_type == ExpressionType.MATH:
        if type(value) not in (int, long, float):
          raise TypeError, "Math expressions can be evaluated only to int, long and float"
        arithmetic_code = "func() { %s = %s }" % (self.expression, value)
        d = arithmetic_compiler.compile(arithmetic_code)["func"]()
        key = d.keys()[0]
        self._variables[key].setValue(d[key], ignore)
      elif self._eq_type == ExpressionType.BOOL:
        key = self._variables.keys()[0]
        self._variables[key].setValue(self._evaluateBool(self.expression, key, value), ignore)
      else:
        raise TypeError, "Wrong expression type"

  def calculateValue(self):
    return self._calculateValueOf(self.expression)

  def _calculateValueOf(self, expression):
    return eval(expression, {}, {k: v.value for k, v in self._variables.iteritems()})


class ReadonlyError(Exception):
  pass


class Constant(Expression):
  def __init__(self, expression):
    super(Constant, self).__init__(expression, {})

  def calculateValue(self):
    return self.expression


class Condition(Expression):
  def __init__(self, cond_expr_list, default_expr, variables):
    # DO NOT call super here. it's on purpose.
    self._variables = variables
    self._cond_expr_list = cond_expr_list
    self._default = default_expr
    self.setReadonly()

  def _get_expression_object(self):
    for cond, expr in self._cond_expr_list:
      if cond.calculateValue():
        return expr
    return self._default

  def calculateValue(self):
    return self._get_expression_object().calculateValue()

  @property
  def expression(self):
    return self._get_expression_object().expression


class Variable(object):
  def __init__(self, value=None):
    self._value = value
    self._dependencies = []
    self._require_redraw = False

  def set_require_redraw(self):
    self._require_redraw = True

  def add_dependency(self, dependency):
    self._dependencies.append(dependency)

  def setInitialValue(self, value):
    self._value = value

  def setValue(self, value, ignore=()):
    old_value = self._value
    self._value = value
    if self._require_redraw:
      # Redraw ...
      redraw_function()
      return
    for dependency in self._dependencies:
      if dependency not in ignore:
        dependency(old_value, value)

  def getValue(self):
    return self._value

  value = property(getValue, setValue)
